import { Cartesian3 } from '../../Source/Cesium.js';
import { Ellipsoid } from '../../Source/Cesium.js';
import { Event } from '../../Source/Cesium.js';
import { GeographicTilingScheme } from '../../Source/Cesium.js';
import { Matrix4 } from '../../Source/Cesium.js';
import { Ray } from '../../Source/Cesium.js';
import { Rectangle } from '../../Source/Cesium.js';
import { WebMercatorProjection } from '../../Source/Cesium.js';
import { WebMercatorTilingScheme } from '../../Source/Cesium.js';
import { Globe } from '../../Source/Cesium.js';
import { ImageryLayer } from '../../Source/Cesium.js';
import { ImageryLayerCollection } from '../../Source/Cesium.js';
import { ImageryLayerFeatureInfo } from '../../Source/Cesium.js';
import { ImageryProvider } from '../../Source/Cesium.js';
import createScene from '../createScene.js';
import pollToPromise from '../pollToPromise.js';
import { when } from '../../Source/Cesium.js';

describe('Scene/ImageryLayerCollection', function() {

    var fakeProvider = {
            isReady : function() { return false; }
        };

    it('tracks the base layer on add', function() {
        var layer1 = new ImageryLayer(fakeProvider);
        var layer2 = new ImageryLayer(fakeProvider);
        var layer3 = new ImageryLayer(fakeProvider);
        var collection = new ImageryLayerCollection();

        expect(layer1.isBaseLayer()).toEqual(false);

        collection.add(layer1);
        expect(layer1.isBaseLayer()).toEqual(true);

        collection.add(layer2);
        expect(layer1.isBaseLayer()).toEqual(true);
        expect(layer2.isBaseLayer()).toEqual(false);

        collection.add(layer3, 0);
        expect(layer1.isBaseLayer()).toEqual(false);
        expect(layer2.isBaseLayer()).toEqual(false);
        expect(layer3.isBaseLayer()).toEqual(true);
    });

    it('tracks the base layer on remove', function() {
        var layer1 = new ImageryLayer(fakeProvider);
        var layer2 = new ImageryLayer(fakeProvider);
        var layer3 = new ImageryLayer(fakeProvider);
        var collection = new ImageryLayerCollection();

        collection.add(layer1);
        collection.add(layer2);
        collection.add(layer3);

        expect(layer1.isBaseLayer()).toEqual(true);
        expect(layer2.isBaseLayer()).toEqual(false);
        expect(layer3.isBaseLayer()).toEqual(false);

        collection.remove(layer1);
        expect(layer2.isBaseLayer()).toEqual(true);
        expect(layer3.isBaseLayer()).toEqual(false);

        collection.remove(layer3);
        expect(layer2.isBaseLayer()).toEqual(true);
    });

    it('updates isBaseLayer on re-add', function() {
        var layer1 = new ImageryLayer(fakeProvider);
        var layer2 = new ImageryLayer(fakeProvider);
        var collection = new ImageryLayerCollection();

        layer1._isBaseLayer = true;
        layer2._isBaseLayer = true;

        collection.add(layer1);
        collection.add(layer2);

        expect(layer1.isBaseLayer()).toEqual(true);
        expect(layer2.isBaseLayer()).toEqual(false);
    });

    it('does not crash when raising and lowering a single layer.', function() {
        var layer1 = new ImageryLayer(fakeProvider);
        var collection = new ImageryLayerCollection();
        collection.add(layer1);

        collection.raise(layer1);
        collection.lower(layer1);

        collection.raiseToTop(layer1);
        collection.lowerToBottom(layer1);
    });

    it('tracks the base layer on raise and lower', function() {
        var layer1 = new ImageryLayer(fakeProvider);
        var layer2 = new ImageryLayer(fakeProvider);
        var layer3 = new ImageryLayer(fakeProvider);
        var collection = new ImageryLayerCollection();

        collection.add(layer1);
        collection.add(layer2);
        collection.add(layer3);

        expect(layer1.isBaseLayer()).toEqual(true);
        expect(layer2.isBaseLayer()).toEqual(false);
        expect(layer3.isBaseLayer()).toEqual(false);

        collection.lower(layer1);
        expect(layer1.isBaseLayer()).toEqual(true);
        expect(layer2.isBaseLayer()).toEqual(false);
        expect(layer3.isBaseLayer()).toEqual(false);

        collection.raise(layer1);
        expect(layer1.isBaseLayer()).toEqual(false);
        expect(layer2.isBaseLayer()).toEqual(true);
        expect(layer3.isBaseLayer()).toEqual(false);

        collection.lower(layer1);
        expect(layer1.isBaseLayer()).toEqual(true);
        expect(layer2.isBaseLayer()).toEqual(false);
        expect(layer3.isBaseLayer()).toEqual(false);
    });

    it('tracks the base layer on raiseToTop to lowerToBottom', function() {
        var layer1 = new ImageryLayer(fakeProvider);
        var layer2 = new ImageryLayer(fakeProvider);
        var layer3 = new ImageryLayer(fakeProvider);
        var collection = new ImageryLayerCollection();

        collection.add(layer1);
        collection.add(layer2);
        collection.add(layer3);

        expect(layer1.isBaseLayer()).toEqual(true);
        expect(layer2.isBaseLayer()).toEqual(false);
        expect(layer3.isBaseLayer()).toEqual(false);

        collection.raiseToTop(layer1);
        expect(layer1.isBaseLayer()).toEqual(false);
        expect(layer2.isBaseLayer()).toEqual(true);
        expect(layer3.isBaseLayer()).toEqual(false);

        collection.lowerToBottom(layer1);
        expect(layer1.isBaseLayer()).toEqual(true);
        expect(layer2.isBaseLayer()).toEqual(false);
        expect(layer3.isBaseLayer()).toEqual(false);
    });

    it('add throws when layer is undefined', function() {
        var collection = new ImageryLayerCollection();

        expect(function() {
            collection.add(undefined);
        }).toThrowDeveloperError();
    });

    it('addImageryProvider throws when imageryProvider is undefined', function() {
        var collection = new ImageryLayerCollection();

        expect(function() {
            collection.addImageryProvider(undefined);
        }).toThrowDeveloperError();
    });

    it('add throws when index is outside valid range', function() {
        var collection = new ImageryLayerCollection();
        var layer1 = new ImageryLayer(fakeProvider);
        var layer2 = new ImageryLayer(fakeProvider);

        expect(function() {
            collection.add(layer1, 1);
        }).toThrowDeveloperError();

        expect(function() {
            collection.add(layer1, -1);
        }).toThrowDeveloperError();

        collection.add(layer1, 0);

        expect(function() {
            collection.add(layer2, -1);
        }).toThrowDeveloperError();

        expect(function() {
            collection.add(layer2, 2);
        }).toThrowDeveloperError();

        collection.add(layer2, 0);
    });

    it('remove ignores request to remove a layer that does not exist in the collection', function() {
        var collection = new ImageryLayerCollection();
        var layer1 = new ImageryLayer(fakeProvider);
        expect(collection.remove(layer1)).toBe(false);
    });

    it('contains works as expected', function() {
        var collection = new ImageryLayerCollection();
        var layer1 = new ImageryLayer(fakeProvider);
        var layer2 = new ImageryLayer(fakeProvider);

        expect(collection.contains(layer1)).toEqual(false);
        expect(collection.contains(layer2)).toEqual(false);

        collection.add(layer1);

        expect(collection.contains(layer1)).toEqual(true);
        expect(collection.contains(layer2)).toEqual(false);

        collection.add(layer2);

        expect(collection.contains(layer1)).toEqual(true);
        expect(collection.contains(layer2)).toEqual(true);

        collection.remove(layer1);

        expect(collection.contains(layer1)).toEqual(false);
        expect(collection.contains(layer2)).toEqual(true);

        collection.remove(layer2);

        expect(collection.contains(layer1)).toEqual(false);
        expect(collection.contains(layer2)).toEqual(false);
    });

    it('get throws if index is not provided', function() {
        var collection = new ImageryLayerCollection();
        expect(function() {
            collection.get();
        }).toThrowDeveloperError();
    });

    it('throws when raising an undefined layer', function() {
        var collection = new ImageryLayerCollection();

        expect(function() {
            collection.raise(undefined);
        }).toThrowDeveloperError();
    });

    it('throws when raising a layer not in the collection', function() {
        var collection = new ImageryLayerCollection();
        var layer1 = new ImageryLayer(fakeProvider);

        expect(function() {
            collection.raise(layer1);
        }).toThrowDeveloperError();
    });

    it('reports whether or not it is destroyed', function() {
        var collection = new ImageryLayerCollection();
        expect(collection.isDestroyed()).toEqual(false);
        collection.destroy();
        expect(collection.isDestroyed()).toEqual(true);
    });

    describe('pickImageryLayerFeatures', function() {
        var scene;
        var globe;
        var camera;

        beforeAll(function() {
            scene = createScene();
            globe = scene.globe = new Globe();
            camera = scene.camera;

            scene.frameState.passes.render = true;
        });

        afterAll(function() {
            scene.destroyForSpecs();
        });

        beforeEach(function() {
            globe.imageryLayers.removeAll();
        });

        /**
         * Repeatedly calls update until the load queue is empty.  Returns a promise that resolves
         * once the load queue is empty.
         */
        function updateUntilDone(globe) {
            // update until the load queue is empty.
            return pollToPromise(function() {
                globe._surface._debug.enableDebugOutput = true;
                scene.render();
                return globe._surface.tileProvider.ready && globe._surface._tileLoadQueueHigh.length === 0 && globe._surface._tileLoadQueueMedium.length === 0 && globe._surface._tileLoadQueueLow.length === 0 && globe._surface._debug.tilesWaitingForChildren === 0;
            });
        }

        it('returns undefined when pick ray does not intersect surface', function() {
            var ellipsoid = Ellipsoid.WGS84;
            camera.lookAt(new Cartesian3(ellipsoid.maximumRadius, 0.0, 0.0), new Cartesian3(0.0, 0.0, 100.0));

            var ray = new Ray(camera.position, Cartesian3.negate(camera.direction, new Cartesian3()));
            var featuresPromise = scene.imageryLayers.pickImageryLayerFeatures(ray, scene);
            expect(featuresPromise).toBeUndefined();
        });

        it('returns undefined when globe has no pickable layers', function() {
            var ellipsoid = Ellipsoid.WGS84;
            camera.lookAt(new Cartesian3(ellipsoid.maximumRadius, 0.0, 0.0), new Cartesian3(0.0, 0.0, 100.0));

            var ray = new Ray(camera.position, camera.direction);
            var featuresPromise = scene.imageryLayers.pickImageryLayerFeatures(ray, scene);
            expect(featuresPromise).toBeUndefined();
        });

        it('returns undefined when ImageryProvider does not implement pickFeatures', function() {
            var provider = {
                ready : true,
                rectangle : Rectangle.MAX_VALUE,
                tileWidth : 256,
                tileHeight : 256,
                maximumLevel : 0,
                minimumLevel : 0,
                tilingScheme : new GeographicTilingScheme(),
                errorEvent : new Event(),
                hasAlphaChannel : true,

                requestImage : function(x, y, level) {
                    return ImageryProvider.loadImage(this, 'Data/Images/Blue.png');
                }
            };

            globe.imageryLayers.addImageryProvider(provider);

            return updateUntilDone(globe).then(function() {
                var ellipsoid = Ellipsoid.WGS84;
                camera.lookAt(new Cartesian3(ellipsoid.maximumRadius, 0.0, 0.0), new Cartesian3(0.0, 0.0, 100.0));

                var ray = new Ray(camera.position, camera.direction);
                var featuresPromise = scene.imageryLayers.pickImageryLayerFeatures(ray, scene);
                expect(featuresPromise).toBeUndefined();
            });
        });

        it('returns undefined when ImageryProvider.pickFeatures returns undefined', function() {
            var provider = {
                ready : true,
                rectangle : Rectangle.MAX_VALUE,
                tileWidth : 256,
                tileHeight : 256,
                maximumLevel : 0,
                minimumLevel : 0,
                tilingScheme : new GeographicTilingScheme(),
                errorEvent : new Event(),
                hasAlphaChannel : true,

                pickFeatures : function(x, y, level, longitude, latitude) {
                    return undefined;
                },

                requestImage : function(x, y, level) {
                    return ImageryProvider.loadImage(this, 'Data/Images/Blue.png');
                }
            };

            globe.imageryLayers.addImageryProvider(provider);

            return updateUntilDone(globe).then(function() {
                var ellipsoid = Ellipsoid.WGS84;
                camera.lookAt(new Cartesian3(ellipsoid.maximumRadius, 0.0, 0.0), new Cartesian3(0.0, 0.0, 100.0));

                var ray = new Ray(camera.position, camera.direction);
                var featuresPromise = scene.imageryLayers.pickImageryLayerFeatures(ray, scene);
                expect(featuresPromise).toBeUndefined();
            });
        });

        it('returns features from one layer', function() {
            var provider = {
                ready : true,
                rectangle : Rectangle.MAX_VALUE,
                tileWidth : 256,
                tileHeight : 256,
                maximumLevel : 0,
                minimumLevel : 0,
                tilingScheme : new GeographicTilingScheme(),
                errorEvent : new Event(),
                hasAlphaChannel : true,

                pickFeatures : function(x, y, level, longitude, latitude) {
                    var deferred = when.defer();
                    setTimeout(function() {
                        var featureInfo = new ImageryLayerFeatureInfo();
                        featureInfo.name = 'Foo';
                        featureInfo.description = '<strong>Foo!</strong>';
                        deferred.resolve([featureInfo]);
                    }, 1);
                    return deferred.promise;
                },

                requestImage : function(x, y, level) {
                    return ImageryProvider.loadImage(this, 'Data/Images/Blue.png');
                }
            };

            var currentLayer = globe.imageryLayers.addImageryProvider(provider);

            return updateUntilDone(globe).then(function() {
                var ellipsoid = Ellipsoid.WGS84;
                camera.lookAt(new Cartesian3(ellipsoid.maximumRadius, 0.0, 0.0), new Cartesian3(0.0, 0.0, 100.0));
                camera.lookAtTransform(Matrix4.IDENTITY);

                var ray = new Ray(camera.position, camera.direction);
                var featuresPromise = scene.imageryLayers.pickImageryLayerFeatures(ray, scene);

                expect(featuresPromise).toBeDefined();

                return featuresPromise.then(function(features) {
                    expect(features.length).toBe(1);
                    expect(features[0].name).toEqual('Foo');
                    expect(features[0].description).toContain('Foo!');
                    expect(features[0].imageryLayer).toBe(currentLayer);
                });
            });
        });

        it('returns features from two layers', function() {
            var provider1 = {
                ready : true,
                rectangle : Rectangle.MAX_VALUE,
                tileWidth : 256,
                tileHeight : 256,
                maximumLevel : 0,
                minimumLevel : 0,
                tilingScheme : new GeographicTilingScheme(),
                errorEvent : new Event(),
                hasAlphaChannel : true,

                pickFeatures : function(x, y, level, longitude, latitude) {
                    var deferred = when.defer();
                    setTimeout(function() {
                        var featureInfo = new ImageryLayerFeatureInfo();
                        featureInfo.name = 'Foo';
                        featureInfo.description = '<strong>Foo!</strong>';
                        deferred.resolve([featureInfo]);
                    }, 1);
                    return deferred.promise;
                },

                requestImage : function(x, y, level) {
                    return ImageryProvider.loadImage(this, 'Data/Images/Blue.png');
                }
            };

            var currentLayer1 = globe.imageryLayers.addImageryProvider(provider1);

            var provider2 = {
                ready : true,
                rectangle : Rectangle.MAX_VALUE,
                tileWidth : 256,
                tileHeight : 256,
                maximumLevel : 0,
                minimumLevel : 0,
                tilingScheme : new GeographicTilingScheme(),
                errorEvent : new Event(),
                hasAlphaChannel : true,

                pickFeatures : function(x, y, level, longitude, latitude) {
                    var deferred = when.defer();
                    setTimeout(function() {
                        var featureInfo = new ImageryLayerFeatureInfo();
                        featureInfo.name = 'Bar';
                        featureInfo.description = '<strong>Bar!</strong>';
                        deferred.resolve([featureInfo]);
                    }, 1);
                    return deferred.promise;
                },

                requestImage : function(x, y, level) {
                    return ImageryProvider.loadImage(this, 'Data/Images/Green.png');
                }
            };

            var currentLayer2 = globe.imageryLayers.addImageryProvider(provider2);

            return updateUntilDone(globe).then(function() {
                var ellipsoid = Ellipsoid.WGS84;
                camera.lookAt(new Cartesian3(ellipsoid.maximumRadius, 0.0, 0.0), new Cartesian3(0.0, 0.0, 100.0));
                camera.lookAtTransform(Matrix4.IDENTITY);

                var ray = new Ray(camera.position, camera.direction);
                var featuresPromise = scene.imageryLayers.pickImageryLayerFeatures(ray, scene);

                expect(featuresPromise).toBeDefined();

                return featuresPromise.then(function(features) {
                    expect(features.length).toBe(2);
                    expect(features[0].name).toEqual('Bar');
                    expect(features[0].description).toContain('Bar!');
                    expect(features[0].imageryLayer).toBe(currentLayer2);
                    expect(features[1].name).toEqual('Foo');
                    expect(features[1].description).toContain('Foo!');
                    expect(features[1].imageryLayer).toBe(currentLayer1);
                });
            });
        });

        it('correctly picks from a terrain tile that is partially covered by correct-level imagery and partially covered by imagery from an ancestor level', function() {
            var provider = {
                ready : true,
                rectangle : new Rectangle(-Math.PI, -WebMercatorProjection.MaximumLatitude, Math.PI, WebMercatorProjection.MaximumLatitude),
                tileWidth : 256,
                tileHeight : 256,
                maximumLevel : 1,
                minimumLevel : 1,
                tilingScheme : new WebMercatorTilingScheme(),
                errorEvent : new Event(),
                hasAlphaChannel : true,

                pickFeatures : function(x, y, level, longitude, latitude) {
                    var deferred = when.defer();
                    setTimeout(function() {
                        var featureInfo = new ImageryLayerFeatureInfo();
                        featureInfo.name = 'L' + level + 'X' + x + 'Y' + y;
                        deferred.resolve([featureInfo]);
                    }, 1);
                    return deferred.promise;
                },

                requestImage : function(x, y, level) {
                    // At level 1, only the northwest quadrant has a valid tile.
                    if (level !== 1 || (x === 0 && y === 0)) {
                        return ImageryProvider.loadImage(this, 'Data/Images/Blue.png');
                    }
                    return when.reject();
                }
            };

            globe.imageryLayers.addImageryProvider(provider);

            camera.setView({ destination : Rectangle.fromDegrees(-180.0, 0, 0, 90) });

            return updateUntilDone(globe).then(function() {
                var ray = new Ray(camera.position, camera.direction);
                var featuresPromise = scene.imageryLayers.pickImageryLayerFeatures(ray, scene);

                expect(featuresPromise).toBeDefined();

                return featuresPromise.then(function(features) {
                    // Verify that we don't end up picking from imagery level 0.
                    expect(features.length).toBe(1);
                    expect(features[0].name).toEqual('L1X0Y0');
                });
            });
        });
    });
}, 'WebGL');
